#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <queue>
#include <pthread.h>
using namespace std;

const int maxcap = 5; // 最大容量

template <class t>
class blockqueue
{
private:
    queue<t> _bq; // 队列,这个队列就是临界资源，需要上锁
    int _cap;     // 容量

    pthread_mutex_t _lock;      // 锁
    pthread_cond_t _full_cond;  // 条件变量,属于生产者线程
    pthread_cond_t _empty_cond; // 属于消费者线程.两者各自在对应的条件变量下等待
    int _csleep_num;
    int _psleep_num;

public:
    bool isfull()
    {
        return _bq.size() >= _cap;
    }
    bool isempty()
    {
        return _bq.size() <= 0;
    }
    blockqueue() : _cap(maxcap), _csleep_num(0), _psleep_num(0)
    {
        pthread_mutex_init(&_lock,nullptr);
        pthread_cond_init(&_full_cond, nullptr);
        pthread_cond_init(&_empty_cond, nullptr);
    }
    ~blockqueue()
    {
    }
    void push(const t &val)
    {
        pthread_mutex_lock(&_lock);
        while (isfull())//一定要使用while，因为可能存在线程的伪唤醒：在这个例子下就是下面的 pthread_cond_wait(_full_cond, &_lock);被唤醒时可能会有多个线程被唤醒，如果用if，那么多个线程就没有再判断if中的条件就唤醒执行了。
        {   
            _psleep_num++;                                       // 等待
            pthread_cond_wait(&_full_cond, &_lock); // 让生产者线程等待。
            // 可见我们对于线程的等待控制一定是在临界区中的，所以就能理解第二个参数为什么是lock了，因为我们让线程在条件变量下等待前需要将锁解开。
            // 当当前线程需要唤醒时（被唤醒时也一定是在临界区内部），就需要先将锁加上
            //调用 pthread_cond_wait 时，线程必须已经持有互斥锁（即参数中指定的互斥锁）。

//pthread_cond_wait 会原子性地释放互斥锁并让线程进入等待状态。

//当条件变量被信号或广播唤醒时，线程会从等待状态中退出，并尝试重新获取互斥锁。在成功获取互斥锁之前，线程会一直阻塞（即等待互斥锁变得可用）。
            _psleep_num--;
        }

        _bq.push(val);

        if(_csleep_num>0) {
            cout<<"唤醒消费者 "<<endl;
            pthread_cond_signal(&_empty_cond);
        }
        pthread_mutex_unlock(&_lock);//将解锁放到唤醒后面是可以的，因为当你唤醒时还没有解锁此时线程不是在条件变量下等待了
        //而是在锁下等待，然后下一行将锁释放，刚被唤醒的线程就能运行了。
    }
    t pop()
    {
        pthread_mutex_lock(&_lock);
        while (isempty()) // 需要进行等待
        {
            _csleep_num++;
            pthread_cond_wait(&_empty_cond, &_lock);
            _csleep_num--;
        }

        t data = _bq.front();
        _bq.pop();
        
        if(_psleep_num>0) {
            cout<<"唤醒生产者 "<<endl;
            pthread_cond_signal(&_full_cond); 
        }
        pthread_mutex_unlock(&_lock);
        return data;
    }
};